package com.litaomn.fang.hotload;

import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;

import com.litaomn.fang.hotload.server.CustomService;
import com.litaomn.fang.hotload.util.LogUtil;
import com.tencent.mm.fang.FangHelpers;
import com.tencent.mm.fang.IFangHookLoadPackage;
import com.tencent.mm.fang.IFangHookZygoteInit;
import com.tencent.mm.fang.XC_MethodHook;
import com.tencent.mm.fang.callbacks.XC_LoadPackage;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.PathClassLoader;

/**
 * @author DX
 * 这种方案建议只在开发调试的时候使用，因为这将损耗一些性能(需要额外加载apk文件)，调试没问题后，直接修改xposed_init文件为正确的类即可
 * 可以实现免重启，由于存在缓存，需要杀死宿主程序以后才能生效
 * 这种免重启的方式针对某些特殊情况的hook无效
 * 例如我们需要implements IFangHookZygoteInit,并将自己的一个服务注册为系统服务，这种就必须重启了
 * Created by DX on 2017/10/4.
 */

public class FangInit implements IFangHookLoadPackage, IFangHookZygoteInit {
    //按照实际使用情况修改下面几项的值
    /**
     * 当前Fang模块的包名,方便寻找apk文件
     */
    private final String modulePackage = "com.litaomn.fang";

    /**
     * 宿主程序的包名(允许多个),过滤无意义的包名,防止无意义的apk文件加载
     */
    private static List<String> hostAppPackages = new ArrayList<>();

    static {
        hostAppPackages.add("com.tencent.mm");
    }

    /**
     * 实际hook逻辑处理类
     */
    private final String handleHookClass = HotLoadEntry.class.getName();
    /**
     * 实际hook逻辑处理类的入口方法
     */
    private final String handleHookMethod = "handleLoadPackage";

    private CustomService mCustomService;

    @Override
    public void initZygote(StartupParam startupParam) throws Throwable {
        //mCustomService = new CustomService();
        //LogUtil.error("new mCustomService");
    }

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparm) throws Throwable {
        if ("android".equals(lpparm.processName)) {
            CustomService.register(lpparm.classLoader);
        }
        //让所有的程序都可以debug
        /*if (hostAppPackages.contains(lpparm.processName)) {
            //将loadPackageParam的classloader替换为宿主程序Application的classloader,解决宿主程序存在多个.dex文件时,有时候ClassNotFound的问题
            FangHelpers.fhm(Application.class, "attach", Context.class, new XC_MethodHook() {
                @Override
                protected void am(XC_MethodHook.MethodHookParam param) throws Throwable {
                    Context context = (Context) param.args[0];
                    lpparm.classLoader = context.getClassLoader();
                    invokeHandleHookMethod(context, modulePackage, handleHookClass, handleHookMethod, lpparm);
                }
            });
        }*/
    }

    /**
     * 安装app以后，系统会在/data/app/下备份了一份.apk文件，通过动态加载这个apk文件，调用相应的方法
     * 这样就可以实现，只需要第一次重启，以后修改hook代码就不用重启了
     *
     * @param context                            context参数
     * @param modulePackageName                  当前模块的packageName
     * @param handleHookClass 指定由哪一个类处理相关的hook逻辑
     */
    private void invokeHandleHookMethod(Context context, String modulePackageName, String handleHookClass, String handleHookMethod, XC_LoadPackage
            .LoadPackageParam loadPackageParam) throws Throwable {
        File apkFile = findApkFile(context, modulePackageName);
        if (apkFile == null) {
            throw new RuntimeException("寻找模块apk失败");
        }
        //加载指定的hook逻辑处理类，并调用它的handleHook方法
        PathClassLoader pathClassLoader = new PathClassLoader(apkFile.getAbsolutePath(), ClassLoader.getSystemClassLoader());
        Class<?> cls = Class.forName(handleHookClass, true, pathClassLoader);
        Object instance = cls.newInstance();
        Method method = cls.getDeclaredMethod(handleHookMethod, XC_LoadPackage.LoadPackageParam.class, Context.class);
        method.invoke(instance, loadPackageParam, context);
    }

    /**
     * 根据包名构建目标Context,并调用getPackageCodePath()来定位apk
     *
     * @param context           context参数
     * @param modulePackageName 当前模块包名
     * @return return apk file
     */
    private File findApkFile(Context context, String modulePackageName) {
        if (context == null) {
            return null;
        }
        try {
            Context moudleContext = context.createPackageContext(modulePackageName, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
            String apkPath = moudleContext.getPackageCodePath();
            return new File(apkPath);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


}
